package dev.ece.suit.hexmap.terrain;

import dev.ece.suit.hexmap.MapCellCreator;

import java.util.List;

import dev.ece.suit.hexmap.MapCell;
import dev.ece.suit.hexmap.Terrain;
import dev.ece.suit.hexmap.TerrainModelCreator;
import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.tools.NormalUtils;

public class SimpleCellCreator implements MapCellCreator {

	private MapCellData[] mapCellDatasCache;
	
	private TerrainModelCreator terrainModelCreator;
	
	private float cellOutSize;
	
	private float cellInSize;
	
	private float waterSize;
	
	private int circles;
	
	private int count;
	
	private Vector3f center;
	private Vector2f centeruv;
	private Vector3f[][] vertice;
	private Vector2f[][] uv;
	private Vector2f[][] blenduv;
	
	public SimpleCellCreator(TerrainModelCreator terrainModelCreator, float cellOutSize, int circles) {
		if(circles < 1) {
			throw new IllegalArgumentException("circles必须大于0");
		}
		if((circles & 1) > 0) {
			throw new IllegalArgumentException("circles必须是偶数");
		}
		this.terrainModelCreator = terrainModelCreator;
		this.circles = circles;
		this.cellOutSize = cellOutSize;
		this.cellInSize = createCellInSize();
		this.waterSize = createWaterSize();
		this.center = createCenter();
		this.centeruv = createCenterUV();
		this.vertice = createVertice();
		this.uv = createUV();
		this.blenduv = createBlendUV();
		this.count = computeCount();
		this.mapCellDatasCache = createMapCellDatasCache();
	}
	
	protected MapCellData[] createMapCellDatasCache() {
		MapCellData[] datas = new MapCellData[getTerrainModelCreator().getTerrains().length];
		for(int i=0; i<datas.length; i++) {
			datas[i] = createMapCellData(getTerrainModelCreator().getTerrains()[i]);
		}
		return datas;
	}
	
	protected MapCellData createMapCellData(Terrain terrain) {
		MapCellData data = new MapCellData();
		float[][] heights = new float[getCircles()][];
		
		for(int i=0; i<getCircles(); i++) {
			heights[i] = terrain.height(this, getVertice()[i], getBlendUV()[i]);
		}
		data.setHeight(terrain.height(this, getCenter(), getCenteruv()));
		data.setHeights(heights);
		data.setCenternormal(new Vector3f());
		data.setNormals(createNormals(
				getCenter(), data.getCenternormal(), data.getHeight(), 
				getVertice(), data.getHeights()));
		return data;
	}
	
	protected float createCellInSize() {
		return getCellOutSize() * (float) Math.sin(Math.toRadians(60));
	}
	
	protected float createWaterSize() {
		return (float) (getCellOutSize() / Math.sin(Math.toRadians(60)));
	}
	
	protected Vector2f[][] createBlendUV() {
		return getUv();
	}
	
	protected Vector2f[][] createUV() {
		Vector2f[][] uv = new Vector2f[getCircles()][];
		for(int i=0; i<getCircles(); i++) {
			uv[i] = createUV(vertice[i]);
		}
		return uv;
	}
	
	protected Vector3f[][] createVertice() {
		Vector3f[][] vertice = new Vector3f[getCircles()][];
		float radians = 0;
		for(int i=0; i<getCircles(); i++) {
			radians = getCellOutSize() * (i + 1) / getCircles();
			vertice[i] = createVertice(radians);
		}
		return vertice;
	}
	
	@Override
	public void fillData(MapCell cell, List<Vector3f> vertice, List<Vector2f> uv, List<Vector3f> normals,
			List<Vector2f> blenduv) {
		Vector3f[] rowVertice = cell.getVertice()[0];
		Vector2f[] rowUV = cell.getUv()[0];
		Vector3f[] rowNormals = cell.getNormals()[0];
		Vector2f[] browUV = getBlendUV()[0];
		int nextCol = 0;
		for(int col=0; col<rowVertice.length; col++) {
			nextCol = (col + 1) % rowVertice.length;
			vertice.add(rowVertice[col]);
			vertice.add(rowVertice[nextCol]);
			vertice.add(cell.getCenter());
			
			uv.add(rowUV[col]);
			uv.add(rowUV[nextCol]);
			uv.add(cell.getCenteruv());
			
			blenduv.add(browUV[col]);
			blenduv.add(browUV[nextCol]);
			blenduv.add(getCenteruv());
			
			normals.add(rowNormals[col]);
			normals.add(rowNormals[nextCol]);
			normals.add(cell.getCenternormal());
		}
		Vector3f[] prowVertice = null;
		Vector2f[] prowUV = null;
		Vector3f[] prowNormals = null;
		Vector2f[] pbrowUV = null;
		int prow = 0;
		for(int row=1; row<cell.getVertice().length; row ++) {
			prow = row - 1;
			prowVertice = cell.getVertice()[prow];
			prowUV = cell.getUv()[prow];
			prowNormals = cell.getNormals()[prow];
			pbrowUV = getBlendUV()[prow];
					
			rowVertice = cell.getVertice()[row];
			rowUV = cell.getUv()[row];
			rowNormals = cell.getNormals()[row];
			browUV = getBlendUV()[row];
			for(int col=0; col<rowVertice.length; col++) {
				nextCol = (col + 1) % rowVertice.length;
				vertice.add(prowVertice[col]);
				vertice.add(rowVertice[col]);
				vertice.add(prowVertice[nextCol]);
				
				uv.add(prowUV[col]);
				uv.add(rowUV[col]);
				uv.add(prowUV[nextCol]);
				
				blenduv.add(pbrowUV[col]);
				blenduv.add(browUV[col]);
				blenduv.add(pbrowUV[nextCol]);
				
				normals.add(prowNormals[col]);
				normals.add(rowNormals[col]);
				normals.add(prowNormals[nextCol]);
				
				vertice.add(prowVertice[nextCol]);
				vertice.add(rowVertice[col]);
				vertice.add(rowVertice[nextCol]);
				
				uv.add(prowUV[nextCol]);
				uv.add(rowUV[col]);
				uv.add(rowUV[nextCol]);
				
				blenduv.add(pbrowUV[nextCol]);
				blenduv.add(browUV[col]);
				blenduv.add(browUV[nextCol]);
				
				normals.add(prowNormals[nextCol]);
				normals.add(rowNormals[col]);
				normals.add(rowNormals[nextCol]);
			}
		}
	}

	@Override
	public MapCell createMapCell(int terrainIndex, int index, int row, int col) {
		int first = index * count();
		MapCellData data = getMapCellDatasCache()[terrainIndex];
		Vector3f center = MapCellCreator.createVertex(getCenter(), data.getHeight());
		Vector3f centernormal = new Vector3f(data.getCenternormal());
		Vector2f centeruv = new Vector2f(getCenteruv());
		Vector3f[][] vertice = MapCellCreator.clone(getVertice(), data.getHeights());
		Vector3f[][] normals = MapCellCreator.clone(data.getNormals());
		Vector2f[][] uv = MapCellCreator.clone(getUv());
		MapCell cell = new MapCell(
				getTerrainModelCreator().getTerrains()[terrainIndex], row, col, first, 
				center, centernormal, centeruv, 
				vertice, normals, uv);
		return cell;
	}

	public Vector3f[][] createNormals(
			Vector3f center, Vector3f centernormal, float height, 
			Vector3f[][] vertice, float[][] heights) {
		Vector3f[][] normals = new Vector3f[vertice.length][];
		//计算中心点法线
		Vector3f[] lineVertice = vertice[0];
		float[] lineHeights = heights[0];
		Vector3f[] lineNormals = new Vector3f[lineVertice.length];
		Vector3f normal = null;
		int endIndex = 0;
		for(int i=0; i<lineVertice.length; i++) {
			endIndex = (i + 1) % lineVertice.length;
			normal = NormalUtils.buildNormal(
					MapCellCreator.createVertex(lineVertice[i], lineHeights[i]), 
					MapCellCreator.createVertex(lineVertice[endIndex], lineHeights[endIndex]), 
					MapCellCreator.createVertex(center, height), null);
			if(i == 0) {
				centernormal.set(normal);
			} else {
				centernormal.add(normal);
				centernormal.normalize();
			}
			if(lineNormals[i] == null) {
				lineNormals[i] = new Vector3f(normal);
			} else {
				lineNormals[i].add(normal);
				lineNormals[i].normalize();
			}
		}
		normals[0] = lineNormals;
		Vector3f[] plineVertice = null;
		float[] plineHeights = null;
		Vector3f[] plineNormals = null;
		int prow = 0;
		for(int row=1; row<vertice.length; row++) {
			prow = row - 1;
			//上一行
			plineVertice = vertice[prow];
			plineHeights = heights[prow];
			plineNormals = normals[prow];
			//当前行
			lineVertice = vertice[row];
			lineHeights = heights[row];
			lineNormals = new Vector3f[lineVertice.length];
			for(int col=0; col<lineVertice.length; col++) {
				endIndex = (col + 1) % lineVertice.length;
				//左边三角
				normal = NormalUtils.buildNormal(
						MapCellCreator.createVertex(plineVertice[col], plineHeights[col]), 
						MapCellCreator.createVertex(lineVertice[col], lineHeights[col]), 
						MapCellCreator.createVertex(plineVertice[endIndex], plineHeights[endIndex]), null);
				MapCellCreator.addNormal(plineNormals[col], normal);
				lineNormals[col] = MapCellCreator.createNormal(lineNormals[col], normal);
				MapCellCreator.addNormal(plineNormals[endIndex], normal);
				//右边三角
				normal = NormalUtils.buildNormal(
						MapCellCreator.createVertex(plineVertice[endIndex], plineHeights[endIndex]), 
						MapCellCreator.createVertex(lineVertice[col], lineHeights[col]), 
						MapCellCreator.createVertex(lineVertice[endIndex], lineHeights[endIndex]), null);
				MapCellCreator.addNormal(plineNormals[endIndex], normal);
				MapCellCreator.addNormal(lineNormals[col], normal);
				lineNormals[endIndex] = MapCellCreator.createNormal(lineNormals[endIndex], normal);
			}
			normals[row] = lineNormals;
		}
		return normals;
	}
	
	public Vector2f[] createUV(Vector3f[] vertice) {
		float size = getCellOutSize() * 2;
		Vector2f[] uv = new Vector2f[vertice.length];
		Vector3f vertex = null;
		for(int i=0; i<uv.length; i++) {
			vertex = vertice[i];
			uv[i] = new Vector2f(
					(getCellOutSize() + vertex.getX()) / size, 
					(getCellOutSize() + vertex.getZ()) / size);
		}
		return uv;
	}
	
	protected Vector3f[] createVertice(float radians) {
		int size = getCircles() * 6;
		Vector3f[] vertice = new Vector3f[size];
		Matrix4f matrix = new Matrix4f();
		Vector3f vertex = null;
		//6个顶点
		for(int i=0; i<6; i++) {
			matrix.identity();
			matrix.rotate(0, 1, 0, 60 * i);
			vertex = new Vector3f(0, 0, - radians);
			vertice[i * getCircles()] = matrix.transform(vertex);
		}
		//6个面
		int start = 0;
		int end = 0;
		Vector3f length = null;
		for(int i=0; i<6; i++) {
			start = i * getCircles();
			end = ((i + 1) * getCircles()) % vertice.length;
			length = new Vector3f();
			length.splus(vertice[start], vertice[end]);
			for(int j=1; j<getCircles(); j++) {
				vertex = new Vector3f(length);
				vertex.mul(j / (float) getCircles());
				vertex.add(vertice[start]);
				vertice[start + j] = vertex;
			}
		}
		return vertice;
	}
	
	protected Vector2f createCenterUV() {
		return new Vector2f(0.5f, 0.5f);
	}
	
	protected Vector3f createCenter() {
		return new Vector3f();
	}
	
	protected int computeCount() {
		int count = getCircles() * 6;
		return count * 3 + (getCircles() - 1) * count * 2 * 3;
	}

	public TerrainModelCreator getTerrainModelCreator() {
		return terrainModelCreator;
	}

	public MapCellData[] getMapCellDatasCache() {
		return mapCellDatasCache;
	}

	@Override
	public Vector2f[][] getBlendUV() {
		return blenduv;
	}

	@Override
	public float getCellOutSize() {
		return cellOutSize;
	}

	@Override
	public float getCellInSize() {
		return cellInSize;
	}

	@Override
	public float getWaterSize() {
		return waterSize;
	}

	@Override
	public int getCircles() {
		return circles;
	}

	@Override
	public int count() {
		return this.count;
	}

	public Vector3f getCenter() {
		return center;
	}

	@Override
	public Vector2f getCenteruv() {
		return centeruv;
	}

	public Vector3f[][] getVertice() {
		return vertice;
	}

	public Vector2f[][] getUv() {
		return uv;
	}
	
	public void destroy() {
		mapCellDatasCache = null;
		terrainModelCreator = null;
		center = null;
		centeruv = null;
		vertice = null;
		uv = null;
		blenduv = null;
	}
}
class MapCellData {
	
	private float height;
	private Vector3f centernormal;
	private float[][] heights;
	private Vector3f[][] normals;
	public float getHeight() {
		return height;
	}
	public void setHeight(float height) {
		this.height = height;
	}
	public Vector3f getCenternormal() {
		return centernormal;
	}
	public void setCenternormal(Vector3f centernormal) {
		this.centernormal = centernormal;
	}
	public float[][] getHeights() {
		return heights;
	}
	public void setHeights(float[][] heights) {
		this.heights = heights;
	}
	public Vector3f[][] getNormals() {
		return normals;
	}
	public void setNormals(Vector3f[][] normals) {
		this.normals = normals;
	}
	
}